<!DOCTYPE html>
<script>
  var isAndroid = navigator.userAgent.includes("Android");
  var isB2G = !isAndroid && /Mobile|Tablet/.test(navigator.userAgent);

  function ok(v, msg) {
    window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
  }

  function is(a, b, msg) {
    ok(a === b, msg + ", expected '" + b + "', got '" + a + "'");
  }

  function todo(v, msg) {
    window.parent.postMessage({status: "todo", result: !!v, message: msg}, "*");
  }

  function finish() {
    window.parent.postMessage({status: "done"}, "*");
  }

  function testFetch() {
    return fetch("fetch.txt").then(function(r) {
      return r.text();
    }).then(function(body) {
      is(body, "so fetch", "A fetch() Request should have the 'fetch' context");
    });
  }

  function testImage() {
    return new Promise(function(resolve, reject) {
      var img = document.createElement("img");
      img.src = "img.jpg";
      // The service worker will respond with an existing image only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      img.onload = resolve;
      img.onerror = reject;
    });
  }

  function testImageSrcSet() {
    return new Promise(function(resolve, reject) {
      var img = document.createElement("img");
      img.srcset = "responsive.jpg 100w";
      // The service worker will respond with an existing image only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      img.onload = resolve;
      img.onerror = reject;
    });
  }

  function testPicture() {
    return new Promise(function(resolve, reject) {
      var pic = document.createElement("picture");
      var img = document.createElement("img");
      pic.appendChild(img);
      img.src = "responsive.jpg?picture";
      // The service worker will respond with an existing image only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      img.onload = resolve;
      img.onerror = reject;
    });
  }

  function testAudio() {
    return new Promise(function(resolve, reject) {
      var audio = document.createElement("audio");
      audio.src = "audio.ogg";
      audio.preload = "metadata";
      // The service worker will respond with an existing audio only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      audio.onloadedmetadata = resolve;
      audio.onerror = reject;
    });
  }

  function testVideo() {
    return new Promise(function(resolve, reject) {
      var video = document.createElement("video");
      video.src = "video.ogg";
      video.preload = "metadata";
      // The service worker will respond with an existing video only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      video.onloadedmetadata = resolve;
      video.onerror = reject;
    });
  }

  function testBeacon() {
    ok(navigator.sendBeacon("beacon.sjs"), "Sending the beacon should succeed");
    // query the context from beacon.sjs
    return fetch("beacon.sjs?queryContext")
      .then(function(r) {
        return r.text();
      }).then(function(body) {
        is(body, "beacon", "The context for the intercepted beacon should be correct");
      });
  }

  function testCSPReport() {
    return new Promise(function(resolve, reject) {
      var iframe = document.createElement("iframe");
      iframe.src = "csp-violate.sjs";
      document.documentElement.appendChild(iframe);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "csp-report") {
          is(e.data.context, "cspreport", "Expected the cspreport context on a CSP violation report");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  function testEmbed() {
    return Promise.resolve().then(function() {
      todo(false, "<embed> tag is not currently intercepted. See Bug 1168676.");
    });

    return new Promise(function(resolve, reject) {
      var embed = document.createElement("embed");
      embed.src = "embed";
      document.documentElement.appendChild(embed);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "embed") {
          is(e.data.context, "embed", "Expected the object context on an embed");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  function testObject() {
    return Promise.resolve().then(function() {
      todo(false, "<object> tag is not currently intercepted. See Bug 1168676");
    });

    return new Promise(function(resolve, reject) {
      var object = document.createElement("object");
      object.data = "object";
      document.documentElement.appendChild(object);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "object") {
          is(e.data.context, "object", "Expected the object context on an object");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  function testFont() {
    return new Promise(function(resolve, reject) {
      var css = '@font-face { font-family: "sw-font"; src: url("font"); }';
      css += '* { font-family: "sw-font"; }';
      var style = document.createElement("style");
      style.appendChild(document.createTextNode(css));
      document.documentElement.appendChild(style);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "font") {
          is(e.data.context, "font", "Expected the font context on an font");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  function testIFrame() {
    return new Promise(function(resolve, reject) {
      var iframe = document.createElement("iframe");
      iframe.src = "iframe";
      document.documentElement.appendChild(iframe);
      // The service worker will respond with an existing document only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      iframe.onload = resolve;
      iframe.onerror = reject;
    });
  }

  function testFrame() {
    return new Promise(function(resolve, reject) {
      var frame = document.createElement("frame");
      frame.src = "frame";
      document.documentElement.appendChild(frame);
      // The service worker will respond with an existing document only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      frame.onload = resolve;
      frame.onerror = reject;
    });
  }

  function testInternal() {
    if (isB2G) {
      // We can't open new windows on b2g, so skip this part of the test there.
      return Promise.resolve();
    }
    return new Promise(function(resolve, reject) {
      // Test this with a new window opened through script.  There are of course
      // other possible ways of testing this too.
      var win = window.open("newwindow", "_blank", "width=100,height=100");
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "newwindow") {
          is(e.data.context, "internal", "Expected the internal context on a newly opened window");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          win.close();
          resolve();
        }
      }, false);
    });
  }

  function testPing() {
    return new Promise(function(resolve, reject) {
      var iframe = document.createElement("iframe");
      iframe.src = "ping.html";
      document.documentElement.appendChild(iframe);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "ping") {
          is(e.data.context, "ping", "Expected the ping context on an anchor ping");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  function testPlugin() {
    return Promise.resolve().then(function() {
      todo(false, "plugins are not currently intercepted. See Bug 1168676.");
    });

    var isMobile = /Mobile|Tablet/.test(navigator.userAgent);
    if (isMobile || parent.isMulet()) {
      // We can't use plugins on mobile, so skip this part of the test there.
      return Promise.resolve();
    }

    return new Promise(function(resolve, reject) {
      var embed = document.createElement("embed");
      embed.type = "application/x-test";
      embed.setAttribute("posturl", "plugin");
      embed.setAttribute("postmode", "stream");
      embed.setAttribute("streammode", "normal");
      embed.setAttribute("src", "fetch.txt");
      document.documentElement.appendChild(embed);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "plugin") {
          is(e.data.context, "plugin", "Expected the plugin context on a request coming from a plugin");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          // Without this, the test leaks in e10s!
          embed.parentNode.removeChild(embed);
          resolve();
        }
      }, false);
    });
  }

  function testScript() {
    return new Promise(function(resolve, reject) {
      var script = document.createElement("script");
      script.src = "script.js";
      document.documentElement.appendChild(script);
      // The service worker will respond with an existing script only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      script.onload = resolve;
      script.onerror = reject;
    });
  }

  function testStyle() {
    return new Promise(function(resolve, reject) {
      var link = document.createElement("link");
      link.rel = "stylesheet";
      link.href = "style.css";
      document.documentElement.appendChild(link);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "style") {
          is(e.data.context, "style", "Expected the style context on a request coming from a stylesheet");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  function testTrack() {
    return new Promise(function(resolve, reject) {
      var video = document.createElement("video");
      var track = document.createElement("track");
      track.src = "track";
      video.appendChild(track);
      document.documentElement.appendChild(video);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "track") {
          is(e.data.context, "track", "Expected the track context on a request coming from a track");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  function testXHR() {
    return new Promise(function(resolve, reject) {
      var xhr = new XMLHttpRequest();
      xhr.open("get", "xhr", true);
      xhr.send();
      // The service worker will respond with an existing resource only if the
      // Request has the correct context, otherwise the Promise will get
      // rejected and the test will fail.
      xhr.onload = resolve;
      xhr.onerror = reject;
    });
  }

  function testXSLT() {
    return new Promise(function(resolve, reject) {
      var iframe = document.createElement("iframe");
      iframe.src = "xml.xml";
      document.documentElement.appendChild(iframe);
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "xslt") {
          is(e.data.context, "xslt", "Expected the xslt context on an XSLT stylesheet");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          // Without this, the test leaks in e10s!
          iframe.parentNode.removeChild(iframe);
          resolve();
        }
      }, false);
    });
  }

  function testWorker() {
    return new Promise(function(resolve, reject) {
      var worker = new Worker("myworker");
      worker.onmessage = function(e) {
        if (e.data == "ack") {
          worker.terminate();
          resolve();
        }
      };
      worker.onerror = reject;
    });
  }

  function testNestedWorker() {
    return new Promise(function(resolve, reject) {
      var worker = new Worker("myparentworker");
      worker.onmessage = function(e) {
        if (e.data == "ack") {
          worker.terminate();
          resolve();
        }
      };
      worker.onerror = reject;
    });
  }

  function testSharedWorker() {
    return new Promise(function(resolve, reject) {
      var worker = new SharedWorker("mysharedworker");
      worker.port.start();
      worker.port.onmessage = function(e) {
        if (e.data == "ack") {
          resolve();
        }
      };
      worker.onerror = reject;
    });
  }

  function testNestedWorkerInSharedWorker() {
    return new Promise(function(resolve, reject) {
      var worker = new SharedWorker("myparentsharedworker");
      worker.port.start();
      worker.port.onmessage = function(e) {
        if (e.data == "ack") {
          resolve();
        }
      };
      worker.onerror = reject;
    });
  }

  function testCache() {
    return new Promise(function(resolve, reject) {
      // Issue an XHR that will be intercepted by the SW in order to start off
      // the test with a RequestContext value that is not the default ("fetch").
      // This needs to run inside a fetch event handler because synthesized
      // RequestContext objects can only have the "fetch" context, and we'd
      // prefer to test the more general case of some other RequestContext value.
      var xhr = new XMLHttpRequest();
      xhr.open("get", "cache", true);
      xhr.send();
      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
        if (e.data.data == "cache") {
          ok(e.data.success, "The RequestContext can be persisted in the cache.");
          navigator.serviceWorker.removeEventListener("message", onMessage);
          resolve();
        }
      }, false);
    });
  }

  var testName = location.search.substr(1);
  window[testName]().then(function() {
    finish();
  }, function(e) {
    ok(false, "A promise was rejected: " + e);
    finish();
  });
</script>
